package br.edu.ufcg.unknown.semantico.arvore;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

import junit.framework.TestCase;
import br.edu.ufcg.unknown.exception.AnalisadorSemanticoUnknownRuntimeException;
import br.edu.ufcg.unknown.exception.UnknownException;
import br.edu.ufcg.unknown.geradorDeCodigo.GeradorDeCodigo;
import br.edu.ufcg.unknown.geradorDeCodigo.GeradorDeCodigo;
import br.edu.ufcg.unknown.lexico.AnalisadorLexico;
import br.edu.ufcg.unknown.lexico.Token;
import br.edu.ufcg.unknown.semantico.Tipo;

public class GeradorDeArvoresTest extends TestCase {

	private GeradorDeArvores gda;

	private AnalisadorLexico al;

	private final File PALAVRAS_RESERVADAS = new File("resources/palavras-reservadas.txt");
	
	private GeradorDeCodigo geradorDeCodigo;

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		gda = new GeradorDeArvores();
		geradorDeCodigo = GeradorDeCodigo.getInstance();
		assertTrue(PALAVRAS_RESERVADAS.exists());
	}
	
	protected void tearDown() throws Exception {
		super.tearDown();
		geradorDeCodigo.resetTemp();
		geradorDeCodigo.resetCodigos();
		geradorDeCodigo.resetTriplaAtual();
		geradorDeCodigo.resetLabel();
	}

	public void testOpAdicao() throws UnknownException {

		init("3 + 4");
		alimentarGerador();

		init("3 + 4");
		Nodo n3 = new Nodo(al.getProxToken(), true);
		Nodo nMais = new Nodo(al.getProxToken(), false);
		Nodo n4 = new Nodo(al.getProxToken(), true);

		nMais.setFilhoEsquerda(n3);
		nMais.setFilhoDireita(n4);

		Nodo raizEsperada = nMais;
		Nodo raizRecebida = this.gda.getRaiz();
		assertEquals(raizEsperada, raizRecebida);

	}

	public void testOpLogico() throws UnknownException {

		init("3 AND 4");
		alimentarGerador();

		init("3 AND 4");
		Nodo n3 = new Nodo(al.getProxToken(), true);
		Nodo nAnd = new Nodo(al.getProxToken(), false);
		Nodo n4 = new Nodo(al.getProxToken(), true);

		nAnd.setFilhoEsquerda(n3);
		nAnd.setFilhoDireita(n4);

		Nodo raizEsperada = nAnd;
		Nodo raizRecebida = this.gda.getRaiz();

		assertTrue(raizEsperada.equals(raizRecebida));
		assertEquals(raizEsperada, raizRecebida);

	}

	public void testMaisDeUmOpLogico() throws UnknownException {
		String entrada = "3 AND 4 OR 2 AND 43";
		init(entrada);
		alimentarGerador();

		init(entrada);

		Nodo n3 = new Nodo(al.getProxToken(), true);

		Nodo nAnd = new Nodo(al.getProxToken(), false);

		Nodo n4 = new Nodo(al.getProxToken(), true);

		Nodo nOr = new Nodo(al.getProxToken(), false);
		Nodo n2 = new Nodo(al.getProxToken(), false);
		Nodo nAnd2 = new Nodo(al.getProxToken(), false);
		Nodo n43 = new Nodo(al.getProxToken(), false);

		nAnd.setFilhoEsquerda(n3);
		nAnd.setFilhoDireita(n4);
		nOr.setFilhoEsquerda(nAnd);
		nOr.setFilhoDireita(n2);

		nAnd2.setFilhoEsquerda(nOr);
		nAnd2.setFilhoDireita(n43);

		Nodo raizEsperada = nAnd2;

		Nodo raizRecebida = this.gda.getRaiz();

		assertTrue(raizEsperada.equals(raizRecebida));
		assertEquals(raizEsperada, raizRecebida);

	}

	public void testOpAdicaoOpLogicoOpMultiplicacao() throws UnknownException {
		String entrada = "3 + 4 AND 2 * 43";
		init(entrada);
		alimentarGerador();

		init(entrada);

		Nodo n3 = new Nodo(al.getProxToken(), true);

		Nodo nMais = new Nodo(al.getProxToken(), false);

		Nodo n4 = new Nodo(al.getProxToken(), true);

		Nodo nAnd = new Nodo(al.getProxToken(), false);
		Nodo n2 = new Nodo(al.getProxToken(), false);
		Nodo nX = new Nodo(al.getProxToken(), false);
		Nodo n43 = new Nodo(al.getProxToken(), false);

		nMais.setFilhoEsquerda(n3);
		nMais.setFilhoDireita(n4);
		nX.setFilhoEsquerda(n2);
		nX.setFilhoDireita(n43);

		nAnd.setFilhoEsquerda(nMais);
		nAnd.setFilhoDireita(nX);

		Nodo raizEsperada = nAnd;

		Nodo raizRecebida = this.gda.getRaiz();
		assertTrue(raizEsperada.equals(raizRecebida));
		assertEquals(raizEsperada, raizRecebida);

	}

	public void testOpRelacional1() throws UnknownException {
		String entrada = "2 + 5 > 3 * 43 + 7";
		init(entrada);
		alimentarGerador();

		init(entrada);

		Nodo n2 = new Nodo(al.getProxToken(), true);

		Nodo nMais1 = new Nodo(al.getProxToken(), false);

		Nodo n5 = new Nodo(al.getProxToken(), true);

		Nodo nMaior = new Nodo(al.getProxToken(), false);
		Nodo n3 = new Nodo(al.getProxToken(), false);
		Nodo nX = new Nodo(al.getProxToken(), false);
		Nodo n43 = new Nodo(al.getProxToken(), false);
		Nodo nMais2 = new Nodo(al.getProxToken(), false);
		Nodo n7 = new Nodo(al.getProxToken(), false);

		nMais1.setFilhoEsquerda(n2);
		nMais1.setFilhoDireita(n5);

		nX.setFilhoDireita(n43);
		nX.setFilhoEsquerda(n3);

		nMais2.setFilhoEsquerda(nX);
		nMais2.setFilhoDireita(n7);

		nMaior.setFilhoEsquerda(nMais1);
		nMaior.setFilhoDireita(nMais2);
		Nodo raizEsperada = nMaior;

		Nodo raizRecebida = this.gda.getRaiz();
		assertTrue(raizEsperada.equals(raizRecebida));
		assertEquals(raizEsperada, raizRecebida);

	}

	public void testOpRelacional2() throws UnknownException {
		String entrada = "2 + 5 > 7 + 3 * 43";
		init(entrada);
		alimentarGerador();

		init(entrada);

		Nodo n2 = new Nodo(al.getProxToken(), true);

		Nodo nMais1 = new Nodo(al.getProxToken(), false);

		Nodo n5 = new Nodo(al.getProxToken(), true);

		Nodo nMaior = new Nodo(al.getProxToken(), false);
		Nodo n7 = new Nodo(al.getProxToken(), false);
		Nodo nMais2 = new Nodo(al.getProxToken(), false);
		Nodo n3 = new Nodo(al.getProxToken(), false);
		Nodo nX = new Nodo(al.getProxToken(), false);
		Nodo n43 = new Nodo(al.getProxToken(), false);

		nMais1.setFilhoEsquerda(n2);
		nMais1.setFilhoDireita(n5);

		nX.setFilhoDireita(n43);
		nX.setFilhoEsquerda(n3);

		nMais2.setFilhoEsquerda(n7);
		nMais2.setFilhoDireita(nX);

		nMaior.setFilhoEsquerda(nMais1);
		nMaior.setFilhoDireita(nMais2);
		Nodo raizEsperada = nMaior;

		Nodo raizRecebida = this.gda.getRaiz();
		assertTrue(raizEsperada.equals(raizRecebida));
		assertEquals(raizEsperada, raizRecebida);

	}

	public void testOpRelacional3() throws UnknownException {
		String entrada = "TRUE > 7 + 3 * 43";
		init(entrada);
		alimentarGerador();

		init(entrada);

		Nodo nTRUE = new Nodo(al.getProxToken(), true);

		Nodo nMaior = new Nodo(al.getProxToken(), false);
		Nodo n7 = new Nodo(al.getProxToken(), false);
		Nodo nMais2 = new Nodo(al.getProxToken(), false);
		Nodo n3 = new Nodo(al.getProxToken(), false);
		Nodo nX = new Nodo(al.getProxToken(), false);
		Nodo n43 = new Nodo(al.getProxToken(), false);

		nX.setFilhoDireita(n43);
		nX.setFilhoEsquerda(n3);

		nMais2.setFilhoEsquerda(n7);
		nMais2.setFilhoDireita(nX);

		nMaior.setFilhoEsquerda(nTRUE);
		nMaior.setFilhoDireita(nMais2);
		Nodo raizEsperada = nMaior;

		Nodo raizRecebida = this.gda.getRaiz();
		assertTrue(raizEsperada.equals(raizRecebida));
		assertEquals(raizEsperada, raizRecebida);

	}

	public void testAvaliacaoDaArvore1() throws UnknownException {
		String entrada = "1 > 2 AND 3 > 4";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();
		
		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 > 1 2");
		strings.add("2. t2 > 3 4");
		strings.add("3. t3 AND t1 t2");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore2() throws UnknownException {
		String entrada = "TRUE AND FALSE";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 AND TRUE FALSE");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore3() throws UnknownException {
		String entrada = "2 >= 3";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 >= 2 3");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore4() throws UnknownException {
		String entrada = "'cadeia1' = 'cadeia2'";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 = 'cadeia1' 'cadeia2'");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore5() throws UnknownException {
		String entrada = "TRUE = FALSE";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 = TRUE FALSE");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore6() throws UnknownException {
		String entrada = "2 + 3 - 1";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("INTEGER", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();
		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 + 2 3");
		strings.add("2. t2 - t1 1");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore7() throws UnknownException {
		String entrada = "1 + 2 > 3 - 4 * 5";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();
		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 + 1 2");
		strings.add("2. t2 * 4 5");
		strings.add("3. t3 - 3 t2");
		strings.add("4. t4 > t1 t3");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore8() throws UnknownException {
		String entrada = "TRUE OR FALSE";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();
		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 OR TRUE FALSE");
		assertEquals(strings, geradorDeCodigo.getCodigos());

		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore9() throws UnknownException {
		String entrada = "1 = 2";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 = 1 2");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore10() throws UnknownException {
		String entrada = "2";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("INTEGER", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();
		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 2");
		assertEquals(strings, geradorDeCodigo.getCodigos());

		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore11() throws UnknownException {
		String entrada = "TRUE";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 TRUE");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore12() throws UnknownException {
		String entrada = "'cadeia1'";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("STRING", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 'cadeia1'");
		assertEquals(strings, geradorDeCodigo.getCodigos());

		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvore13() throws UnknownException {
		String entrada = "TRUE AND TRUE";
		init(entrada);
		alimentarGerador();

		Nodo nodoEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS)
				.getProxToken());
		Nodo nodoRecebido = this.gda.resolver();

		List<String> strings = new LinkedList<String>();
		strings.add("1. t1 AND TRUE TRUE");
		assertEquals(strings, geradorDeCodigo.getCodigos());
		assertEquals(nodoEsperado, nodoRecebido);
	}

	public void testAvaliacaoDaArvoreLancaErro1() throws UnknownException {
		String entrada = "TRUE > 7 + 3 * 43";
		init(entrada);
		alimentarGerador();

		Tipo tEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS).getProxToken())
				.getTipo();
		Tipo tRecebido = new Nodo(new AnalisadorLexico("INTEGER", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		AnalisadorSemanticoUnknownRuntimeException exp = new AnalisadorSemanticoUnknownRuntimeException(
				tEsperado, tRecebido, 0, 0);

		try {
			this.gda.resolver();
			fail("Erro! Era pra ter sido lançada uma exceção semântica de tipos incompatíveis!");
		} catch (AnalisadorSemanticoUnknownRuntimeException e) {
			assertEquals(exp, e);
		}

	}

	public void testAvaliacaoDaArvoreLancaErro2() throws UnknownException {
		String entrada = "7 + 3 * 43 > TRUE";
		init(entrada);
		alimentarGerador();

		Tipo tEsperado = new Nodo(new AnalisadorLexico("INTEGER", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		Tipo tRecebido = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS).getProxToken())
				.getTipo();
		AnalisadorSemanticoUnknownRuntimeException exp = new AnalisadorSemanticoUnknownRuntimeException(
				tEsperado, tRecebido);

		try {
			this.gda.resolver();
			fail("Erro! Era pra ter sido lançada uma exceção semântica de tipos incompatíveis!");
		} catch (AnalisadorSemanticoUnknownRuntimeException e) {
			assertEquals(exp, e);
		}

	}

	public void testAvaliacaoDaArvoreLancaErro3() throws UnknownException {
		String entrada = "TRUE > 'cadeia1'";
		init(entrada);
		alimentarGerador();

		Tipo tEsperado = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS).getProxToken())
				.getTipo();
		Tipo tRecebido = new Nodo(new AnalisadorLexico("STRING", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		AnalisadorSemanticoUnknownRuntimeException exp = new AnalisadorSemanticoUnknownRuntimeException(
				tEsperado, tRecebido);

		try {
			this.gda.resolver();
			fail("Erro! Era pra ter sido lançada uma exceção semântica de tipos incompatíveis!");
		} catch (AnalisadorSemanticoUnknownRuntimeException e) {
			assertEquals(exp, e);
		}

	}

	public void testAvaliacaoDaArvoreLancaErro4() throws UnknownException {
		String entrada = "'cadeia1' > TRUE";
		init(entrada);
		alimentarGerador();

		Tipo tEsperado = new Nodo(new AnalisadorLexico("STRING", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		Tipo tRecebido = new Nodo(new AnalisadorLexico("TRUE", PALAVRAS_RESERVADAS).getProxToken())
				.getTipo();
		AnalisadorSemanticoUnknownRuntimeException exp = new AnalisadorSemanticoUnknownRuntimeException(
				tEsperado, tRecebido);

		try {
			this.gda.resolver();
			fail("Erro! Era pra ter sido lançada uma exceção semântica de tipos incompatíveis!");
		} catch (AnalisadorSemanticoUnknownRuntimeException e) {
			assertEquals(exp, e);
		}

	}

	public void testAvaliacaoDaArvoreLancaErro5() throws UnknownException {
		String entrada = "2 = 'cadeia1'";
		init(entrada);
		alimentarGerador();

		Tipo tEsperado = new Nodo(new AnalisadorLexico("INTEGER", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		Tipo tRecebido = new Nodo(new AnalisadorLexico("STRING", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		AnalisadorSemanticoUnknownRuntimeException exp = new AnalisadorSemanticoUnknownRuntimeException(
				tEsperado, tRecebido);

		try {
			this.gda.resolver();
			fail("Erro! Era pra ter sido lançada uma exceção semântica de tipos incompatíveis!");
		} catch (AnalisadorSemanticoUnknownRuntimeException e) {
			assertEquals(exp, e);
		}

	}

	public void testAvaliacaoDaArvoreLancaErro6() throws UnknownException {
		String entrada = "'cadeia1' = 2";
		init(entrada);
		alimentarGerador();

		Tipo tEsperado = new Nodo(new AnalisadorLexico("STRING", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		Tipo tRecebido = new Nodo(new AnalisadorLexico("INTEGER", PALAVRAS_RESERVADAS)
				.getProxToken()).getTipo();
		AnalisadorSemanticoUnknownRuntimeException exp = new AnalisadorSemanticoUnknownRuntimeException(
				tEsperado, tRecebido);

		try {
			this.gda.resolver();
			fail("Erro! Era pra ter sido lançada uma exceção semântica de tipos incompatíveis!");
		} catch (AnalisadorSemanticoUnknownRuntimeException e) {
			assertEquals(exp, e);
		}

	}

	// Métodos auxiliares

	private void init(String input) throws UnknownException {
		al = new AnalisadorLexico(input, PALAVRAS_RESERVADAS);
	}

	private void alimentarGerador() {
		Token tk = null;

		while ((tk = al.getProxToken()) != null) {
			this.gda.addNodo(new Nodo(tk));
		}
	}

}
